var __createBinding =
    (this && this.__createBinding) ||
    (Object.create
        ? (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              var desc = Object.getOwnPropertyDescriptor(m, k);
              if (
                  !desc ||
                  ("get" in desc
                      ? !m.__esModule
                      : desc.writable || desc.configurable)
              ) {
                  desc = { enumerable: true, get: () => m[k] };
              }
              Object.defineProperty(o, k2, desc);
          }
        : (o, m, k, k2) => {
              if (k2 === undefined) k2 = k;
              o[k2] = m[k];
          });
var __setModuleDefault =
    (this && this.__setModuleDefault) ||
    (Object.create
        ? (o, v) => {
              Object.defineProperty(o, "default", {
                  enumerable: true,
                  value: v,
              });
          }
        : (o, v) => {
              o.default = v;
          });
var __importStar =
    (this && this.__importStar) ||
    ((mod) => {
        if (mod?.__esModule) return mod;
        var result = {};
        if (mod != null)
            for (var k in mod)
                if (k !== "default" && Object.hasOwn(mod, k))
                    __createBinding(result, mod, k);
        __setModuleDefault(result, mod);
        return result;
    });
Object.defineProperty(exports, "__esModule", { value: true });
exports.findConstantVariable =
    exports.ConstantAssignmentVariable =
    exports.ConstantDeclarationVariable =
    exports.ConstantVariable =
        void 0;
const t = __importStar(require("@babel/types"));
class ConstantVariable {
    /**
     * Creates a new constant variable.
     * @param name The name of the variable.
     * @param binding The binding.
     * @param expression The value the variable holds.
     */
    constructor(name, binding, expression) {
        this.name = name;
        this.binding = binding;
        this.expression = expression;
    }
}
exports.ConstantVariable = ConstantVariable;
class ConstantDeclarationVariable extends ConstantVariable {
    /**
     * Creates a new constant variable that is declared and initialised immediately.
     * @param declaratorPath The path of the variable declarator.
     * @param name The name of the variable.
     * @param binding The binding.
     * @param expression The value the variable holds.
     */
    constructor(declaratorPath, name, binding, expression) {
        super(name, binding, expression);
        this.declaratorPath = declaratorPath;
    }
    /**
     * Removes the variable.
     */
    remove() {
        this.declaratorPath.remove();
    }
}
exports.ConstantDeclarationVariable = ConstantDeclarationVariable;
class ConstantAssignmentVariable extends ConstantVariable {
    /**
     * Creates a new constant variable that is declared with no value then assigned to later.
     * @param declaratorPath The path of the variable declarator.
     * @param assignmentPath The path of the assignment to the variable.
     * @param name The name of the variable.
     * @param binding The binding.
     * @param expression The value the variable holds.
     */
    constructor(declaratorPath, assignmentPath, name, binding, expression) {
        super(name, binding, expression);
        this.declaratorPath = declaratorPath;
        this.assignmentPath = assignmentPath;
    }
    /**
     * Removes the variable.
     */
    remove() {
        this.declaratorPath.remove();
        this.assignmentPath.remove();
    }
}
exports.ConstantAssignmentVariable = ConstantAssignmentVariable;
/**
 * Checks whether a node is initialising a 'constant' variable and returns the variable if so.
 * @param path The path.
 * @param isType The function that determines whether the expression is of the desired type.
 * @returns The constant variable or undefined.
 */
function findConstantVariable(path, isType, canBeFunction = false) {
    if (
        path.isVariableDeclarator() &&
        t.isIdentifier(path.node.id) &&
        path.node.init !== undefined &&
        isType(path.node.init)
    ) {
        const name = path.node.id.name;
        const binding = path.scope.getBinding(name);
        return binding && isConstantBinding(path, binding)
            ? new ConstantDeclarationVariable(
                  path,
                  name,
                  binding,
                  path.node.init,
              )
            : undefined;
    }
    // essentially same as declarator but allows function declarations
    else if (
        canBeFunction &&
        path.isFunctionDeclaration() &&
        t.isIdentifier(path.node.id) &&
        isType(path.node)
    ) {
        const name = path.node.id.name;
        const binding = path.scope.getBinding(name);
        return binding && isConstantBinding(path, binding)
            ? new ConstantDeclarationVariable(path, name, binding, path.node)
            : undefined;
    } else if (
        path.isAssignmentExpression() &&
        path.node.operator === "=" &&
        t.isIdentifier(path.node.left) &&
        isType(path.node.right)
    ) {
        const name = path.node.left.name;
        const binding = path.scope.getBinding(name);
        return binding &&
            ((binding.path.isVariableDeclarator() &&
                binding.path.node.init === undefined) ||
                binding.path.parentKey === "params") && // either variable declarator with no initialiser or parameter of function
            binding.constantViolations.length === 1 &&
            binding.constantViolations[0].node === path.node
            ? new ConstantAssignmentVariable(
                  binding.path,
                  path,
                  name,
                  binding,
                  path.node.right,
              )
            : undefined;
    }
    return undefined;
}
exports.findConstantVariable = findConstantVariable;
/**
 * Returns whether a binding is constant for our purposes. Babel views
 * 'var' declarations within loops as non constants so this acts as a fix
 * for that.
 * @param path The path.
 * @param binding The binding.
 * @returns
 */
function isConstantBinding(path, binding) {
    return (
        binding.constant ||
        (binding.constantViolations.length === 1 &&
            path.node === binding.path.node &&
            path.node === binding.constantViolations[0].node)
    );
}
